package security;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import security.exception.KeyLoadingFailedException;

/**
 * Class used to send and receive Messages which are RSA encoded and decoded
 * over a TCP-Channel
 *
 * @author Group 95
 */
public class RSAChannel implements Channel {

    private static final Logger logger = Logger.getLogger("RSAChannel");
    private Channel channel;
    private PrivateKey pkReceive;
    private PublicKey pkSend;
    private String userPkReceive;
    private String userPkSend;

    {
        logger.setLevel(Level.ALL);
    }

    public RSAChannel(Channel channel, PrivateKey pkReceive) {
        this(channel, pkReceive, null, null, null);
    }

    public RSAChannel(Channel channel, PrivateKey pkReceive, PublicKey pkSend, String userPkReceive, String userPkSend) {
        logger.log(Level.INFO, "RSAChannel channel: {0}, pkReceive: {1}, pkToSend: {2}, userPkReceive: {3}, userPkSend: {4}", new Object[]{channel, pkReceive, pkSend, userPkReceive, userPkSend});
        this.channel = channel;
        this.pkReceive = pkReceive;
        this.pkSend = pkSend;
        this.userPkReceive = userPkReceive;
        this.userPkSend = userPkSend;
        Security.addProvider(new BouncyCastleProvider());
    }

    public void setUserPkSend(String userPkSend) {
        this.userPkSend = userPkSend;
    }

    @Override
    public byte[] receiveMessage() throws IOException {
        byte[] bRet = decodeMessage(channel.receiveMessage());
        logger.log(Level.INFO, "receiveMessage() received: {0}", new Object[]{bRet});
        return bRet;
    }

    @Override
    public byte[] reReceiveMessage() {
        return decodeMessage(channel.reReceiveMessage());
    }

    private byte[] decodeMessage(byte[] message) {
        byte[] bRet = null;
        try {
            logger.log(Level.INFO, "decodeMessage() message: {0}", new Object[]{message});
            if (pkReceive == null) {
                logger.info("decrypt pkrReceive null");
                pkReceive = KeyLoader.loadPrivateKey(userPkReceive);
            }
            //Cipher cipher=Cipher.getInstance("OAEPWithSHA256AndMGF1Padding","BC");
            Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");
            logger.info("cipher created");
            cipher.init(Cipher.DECRYPT_MODE, pkReceive);
            logger.info("cipher init");
            message = cipher.doFinal(message);
            logger.log(Level.INFO, "decrypted: {0}", new Object[]{message});
            bRet = new byte[message.length];
            System.arraycopy(message, 0, bRet, 0, message.length);
            
        } catch (KeyLoadingFailedException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            logger.log(Level.SEVERE, "NoSuchAlgorithmException", ex);
        } catch (NoSuchProviderException ex) {
            logger.log(Level.SEVERE, "NoSuchProviderException", ex);
        } catch (NoSuchPaddingException ex) {
            logger.log(Level.SEVERE, "NoSuchPaddingException", ex);
        } catch (IllegalBlockSizeException ex) {
            logger.log(Level.SEVERE, "IllegalBlockSizeException", ex);
        } catch (InvalidKeyException ex) {
            logger.log(Level.SEVERE, "InvalidKeyException", ex);
        } catch (BadPaddingException ex) {
            logger.log(Level.SEVERE, "BadPaddingException", ex);
        }
        return bRet;
    }

    @Override
    public void sendMessage(byte[] message) throws IOException {
        logger.log(Level.INFO, "sendMessage strMessage: {0}", new Object[]{message});
//        strMessage = Base64Coder.base64encodeParts(strMessage);
//        logger.log(Level.INFO, "Base64 encoded: strMessage: {0}", new Object[]{strMessage});

        try {
            if (pkSend == null) {
                pkSend = KeyLoader.loadPublicKey(userPkSend);
            }
            //Cipher cipher=Cipher.getInstance("OAEPWithSHA256AndMGF1Padding","BC");
            Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");
            cipher.init(Cipher.ENCRYPT_MODE, pkSend);
            message = cipher.doFinal(message);
            logger.log(Level.INFO, "encrypted: {0}", new Object[]{message});
            channel.sendMessage(message);

        } catch (NoSuchAlgorithmException ex) {
            logger.log(Level.SEVERE, "NoSuchAlgorithmException", ex);
        } catch (NoSuchPaddingException ex) {
            logger.log(Level.SEVERE, "NoSuchPaddingException", ex);
        } catch (NoSuchProviderException ex) {
            logger.log(Level.SEVERE, "NoSuchProviderException", ex);
        } catch (IllegalBlockSizeException ex) {
            logger.log(Level.SEVERE, "IllegalBlockSizeException", ex);
        } catch (InvalidKeyException ex) {
            logger.log(Level.SEVERE, "InvalidKeyException", ex);
        } catch (BadPaddingException ex) {
            logger.log(Level.SEVERE, "BadPaddingException", ex);
        } catch (KeyLoadingFailedException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void close() {
        channel.close();
    }
    
    /**
     * Checks if something is readable from the underlying socket
     * @return true if something can be read from the socket
     */
    public boolean receivable() throws IOException
    {
        return this.channel.receivable();
    }    
}
